obsidian_curriculum_page
Amazon Bedrock (Claude 5) & 로컬 벡터 DB (FAISS) 연동 RAG 실습 가이드
이 페이지는 광주소프트웨어마이스터고등학교(GSM) AI 해커톤 교육의 실습 코드 가이드라인입니다. 아래 코드를 순서대로 복사하여 EC2 서버 내에서 실행하고 서비스를 구축할 수 있습니다.
1. 실습 환경 준비 (라이브러리 설치)
EC2 인스턴스에 접속한 후, 아래 명령어를 실행하여 필요한 패키지를 전역 환경에 설치합니다.
# 시스템 패키지 업데이트 및 AWS CLI 설치
sudo apt-get update
sudo apt-get install -y unzip curl
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# 파이썬 RAG 핵심 패키지 설치
pip3 install boto3 streamlit langchain langchain-aws langchain-community faiss-cpu pypdf --break-system-packages
💡 AWS 자격 증명 점검 명령어:
aws sts get-caller-identity출력 결과에 EC2 인스턴스에 할당된 Role(예:
gsm-student-role또는gsm-instructor-role)이 정상적으로 나타나는지 확인합니다.
2. 실습 소스 코드 파일 구성
💡 리눅스 터미널(EC2)에서 파일 생성 및 코드 작성하는 방법 (Nano 편집기)
리눅스 터미널 환경에서 아래 소스 코드를 생성하고 붙여넣으려면 nano 편집기를 사용합니다:
- 파일 열기/생성: 터미널에
nano <파일명>을 입력합니다. (예:nano bedrock_simple_test.py) - 코드 붙여넣기: 가이드의 소스코드를 전체 복사한 후, 터미널 창에 마우스 오른쪽 버튼을 클릭하여 붙여넣습니다. (Mac 터미널은
Cmd + V) - 저장 및 종료:
- 저장: 키보드 단축키
Ctrl + O➡️Enter입력 - 종료: 키보드 단축키
Ctrl + X입력 (터미널 원래 화면으로 복귀)
- 저장: 키보드 단축키
① 패키지 버전 목록 (requirements.txt)
프로젝트 폴더 내에 생성하여 버전 충돌을 방지합니다.
boto3>=1.34.0
streamlit>=1.32.0
langchain>=0.2.0
langchain-aws>=0.1.0
langchain-community>=0.2.0
faiss-cpu>=1.7.4
pypdf>=4.0.0
② Bedrock 기초 호출 테스트 (bedrock_simple_test.py)
IAM 권한이 올바르게 잡혀 Bedrock API가 성공적으로 통신하는지 가장 먼저 점검하는 코드입니다.
import json
import boto3
def test_bedrock():
# Bedrock Runtime Client 초기화 (자동으로 EC2 IAM 인스턴스 역할 인지)
bedrock_runtime = boto3.client(
service_name='bedrock-runtime',
region_name='us-east-1'
)
# Claude 5 Sonnet 교차 리전 추론 프로필 ID
model_id = "us.anthropic.claude-sonnet-5"
prompt = "AWS EC2 인스턴스 프로파일을 통해 Amazon Bedrock Claude 5 연동에 최종 성공했습니다. 축하 메시지 한 줄 출력해줘."
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 300,
"messages": [
{"role": "user", "content": prompt}
]
})
print("Sending prompt to Bedrock Claude...")
try:
response = bedrock_runtime.invoke_model(
modelId=model_id,
body=body
)
response_body = json.loads(response.get('body').read())
answer = response_body['content'][0]['text']
print("\n--- Claude Response ---")
print(answer)
print("-----------------------")
except Exception as e:
print(f"Error calling Bedrock: {e}")
if __name__ == "__main__":
test_bedrock()
③ 로컬 벡터 DB 빌더 (bedrock_faiss_indexer.py)
S3 버킷에서 원천 지식 파일을 다운로드하여 텍스트 청킹을 수행한 뒤, 로컬 FAISS 벡터 데이터베이스(faiss_index/)로 생성하는 코드입니다.
import os
import boto3
from langchain_aws import BedrockEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
def build_local_vector_db():
# TODO: 본인 계정의 S3 버킷명으로 반드시 변경해 주세요!
bucket_name = "gsm-instructor-bucket-tbit-498307943987"
s3_file_key = "s3_test.txt"
local_file_name = "downloaded_context.txt"
# 1. S3에서 파일 다운로드
print("1. Downloading raw file from S3...")
s3 = boto3.client('s3')
try:
s3.download_file(bucket_name, s3_file_key, local_file_name)
print(f" Successfully downloaded '{s3_file_key}' to '{local_file_name}'")
except Exception as e:
print(f" Error downloading from S3: {e}")
print(" If downloading fails, checking for local 'rich_context.txt' as fallback...")
if os.path.exists("rich_context.txt"):
local_file_name = "rich_context.txt"
print(" Using local 'rich_context.txt' fallback.")
else:
print(" No fallback file found. Indexing aborted.")
return
# 2. 파일 텍스트 로드 및 청킹 (Chunking)
print("2. Splitting text into chunks...")
with open(local_file_name, 'r', encoding='utf-8') as f:
text = f.read()
# 청크 크기 100자, 중복 20자 설정
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
docs = text_splitter.create_documents([text])
print(f" Created {len(docs)} text chunks.")
# 3. Bedrock Titan Embedding v2 모델 설정 (버지니아 리전)
print("3. Initializing Bedrock Titan Text Embeddings...")
bedrock_client = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')
embeddings = BedrockEmbeddings(
client=bedrock_client,
model_id="amazon.titan-embed-text-v2:0"
)
# 4. 로컬 FAISS 벡터 DB 구축 및 로컬 저장
print("4. Generating vectors and saving to local FAISS index...")
db = FAISS.from_documents(docs, embeddings)
db.save_local("faiss_index")
print(" Success! Vector DB saved locally as folder './faiss_index'\n")
if __name__ == "__main__":
build_local_vector_db()
④ Streamlit 기반 실시간 스트리밍 웹 RAG 챗봇 (app.py)
Day 3에 학생들이 최종적으로 완성할 웹 UI 애플리케이션 데모입니다. 로컬 FAISS 벡터 검색 결과를 병합하여 Claude 5 API를 호출하고 한 글자씩 타이핑되듯 보여주는 실시간 스트리밍을 지원하며, 추가 라우팅 페이지를 통해 웹 UI에서 직접 S3 문서 업로드 및 로컬 벡터 DB(FAISS) 즉시 갱신을 수행할 수 있습니다.
import os
import json
import boto3
import streamlit as st
from langchain_aws import BedrockEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. Page Config
st.set_page_config(page_title="GSM AI Hackathon Portal", page_icon="🤖", layout="centered")
# AWS 및 S3/Bedrock 설정
# TODO: 본인 계정의 S3 버킷명으로 반드시 변경해 주세요!
BUCKET_NAME = "gsm-instructor-bucket-tbit-498307943987"
S3_FILE_KEY = "s3_test.txt" # 기본 RAG 참고용 파일 키
# 2. AWS Client Initialization
@st.cache_resource
def get_aws_clients():
s3_client = boto3.client('s3')
bedrock_client = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')
return s3_client, bedrock_client
s3_client, bedrock_client = get_aws_clients()
# 3. Bedrock Embeddings Config
def get_embeddings():
return BedrockEmbeddings(
client=bedrock_client,
model_id="amazon.titan-embed-text-v2:0"
)
# 4. Local FAISS DB Loader (동적 갱신 반영을 위해 캐싱 제거)
def load_vector_db():
embeddings = get_embeddings()
try:
db = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
return db
except Exception as e:
return None
# 5. Multi-Page Navigation (Sidebar)
st.sidebar.title("📌 메뉴 구성")
page = st.sidebar.radio("원하시는 페이지를 선택하세요:", ["💬 RAG 챗봇", "📁 S3 문서 업로드 & DB 갱신"])
# ==========================================
# PAGE 1: RAG Chatbot
# ==========================================
if page == "💬 RAG 챗봇":
st.title("💬 RAG Q&A 챗봇 서비스")
st.write("로컬 벡터 데이터베이스(FAISS)를 조회하여 팩트 기반의 답변을 실시간 스트리밍합니다.")
# 디스크에서 실시간으로 인덱스 로드
db = load_vector_db()
if db is None:
st.warning("로컬 FAISS 인덱스를 불러올 수 없습니다. '📁 S3 문서 업로드 & DB 갱신' 메뉴에서 인덱스를 먼저 빌드해 주세요.")
# Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = []
# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Stream Response Generator from Bedrock
def stream_bedrock_response(prompt_text):
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 800,
"messages": [
{"role": "user", "content": prompt_text}
]
})
try:
response = bedrock_client.invoke_model_with_response_stream(
modelId="us.anthropic.claude-sonnet-5",
body=body
)
for event in response.get('body'):
chunk = json.loads(event.get('chunk').get('bytes').decode('utf-8'))
if chunk.get('type') == 'content_block_delta':
yield chunk.get('delta', {}).get('text', '')
except Exception as e:
yield f"\n\n⚠️ Error calling Bedrock: {e}"
# Chat logic
if db is not None:
if query := st.chat_input("질문을 입력해 주세요..."):
with st.chat_message("user"):
st.markdown(query)
st.session_state.messages.append({"role": "user", "content": query})
# FAISS similarity search (k=4)
docs = db.similarity_search(query, k=4)
context_text = "\n---\n".join([doc.page_content for doc in docs])
# Augmented prompt
augmented_prompt = f"""
당신은 친절한 안내원입니다. 아래 주어진 [참고문서]를 바탕으로 질문에 답하세요.
참고문서에 없는 질문은 모른다고 명확히 답해야 합니다.
[참고문서]
{context_text}
질문: {query}
답변:
"""
with st.chat_message("assistant"):
response_placeholder = st.empty()
full_response = ""
for chunk in stream_bedrock_response(augmented_prompt):
full_response += chunk
response_placeholder.markdown(full_response + "▌")
response_placeholder.markdown(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})
# ==========================================
# PAGE 2: Document Upload & Index Update
# ==========================================
elif page == "📁 S3 문서 업로드 & DB 갱신":
st.title("📁 S3 문서 업로드 및 벡터 DB 갱신")
st.write("로컬 PC의 문서를 S3 버킷으로 업로드하고, 실시간으로 벡터 데이터베이스(FAISS)를 갱신합니다.")
st.info(f"현재 타겟 S3 버킷: `{BUCKET_NAME}`")
# 1. File Uploader Form
uploaded_file = st.file_uploader("업로드할 텍스트 파일(.txt)을 선택해 주세요", type=["txt"])
if uploaded_file is not None:
file_details = {"FileName": uploaded_file.name, "FileType": uploaded_file.type, "FileSize": f"{uploaded_file.size} Bytes"}
st.write(file_details)
# 원클릭 업로드 및 인덱스 갱신 통합 버튼
if st.button("S3 업로드 및 벡터 DB 즉시 갱신 실행"):
progress_bar = st.progress(0)
status_text = st.empty()
try:
# 1단계: S3 업로드
status_text.text("1/5 단계: S3 버킷으로 파일 업로드 중...")
s3_client.upload_fileobj(uploaded_file, BUCKET_NAME, S3_FILE_KEY)
progress_bar.progress(20)
# 2단계: S3에서 최신 문서 다운로드 (동기화 검증)
status_text.text("2/5 단계: S3에서 방금 업로드된 최신 문서 다운로드 중...")
local_file_name = "downloaded_context.txt"
s3_client.download_file(BUCKET_NAME, S3_FILE_KEY, local_file_name)
progress_bar.progress(40)
# 3단계: 텍스트 로드 및 청킹
status_text.text("3/5 단계: 문서 데이터를 읽고 텍스트 청크 분할 중...")
with open(local_file_name, 'r', encoding='utf-8') as f:
text = f.read()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = text_splitter.create_documents([text])
progress_bar.progress(60)
# 4단계: 임베딩 및 인덱스 생성
status_text.text("4/5 단계: Titan 임베딩 생성 및 벡터 DB 빌드 중...")
embeddings = get_embeddings()
db = FAISS.from_documents(docs, embeddings)
progress_bar.progress(80)
# 5단계: 로컬 저장
status_text.text("5/5 단계: 로컬 디스크에 faiss_index 저장 완료 중...")
db.save_local("faiss_index")
progress_bar.progress(100)
st.success(f"성공! 파일이 `s3://{BUCKET_NAME}/{S3_FILE_KEY}` 경로로 업로드되었으며, 벡터 DB 갱신이 최종 완료되었습니다. (총 {len(docs)}개의 청크 데이터베이스화 성공)")
status_text.text("준비 완료! '💬 RAG 챗봇' 메뉴로 이동하여 질문해 보세요.")
# 대화 기록 세션 초기화
st.session_state.messages = []
except Exception as e:
st.error(f"작업 도중 에러가 발생했습니다: {e}")
⚡ 3. 정상 실행 검증 명령어
① 로컬 벡터 DB 빌드 실행
python3 bedrock_faiss_indexer.py
체크포인트: 실행 폴더 경로에
faiss_index/폴더가 생성되었고 안에index.faiss,index.pkl파일이 들어있는지ls -la faiss_index/명령어로 확인합니다.
② Streamlit 웹 애플리케이션 가동
python3 -m streamlit run app.py --server.address 0.0.0.0
체크포인트: 서버 구동 후 본인 컴퓨터 웹 브라우저에서
http://<EC2-퍼블릭IP>:8501주소로 정상 접속되는지 확인합니다.
💡 RAG 기능 자가진단 질문 (환각 현상 방지 테스트)
- 질문 1:
교육 일정이 어떻게 되지?
👉 기대 답변: "2026년 7월 15일부터 7월 17일까지 3일간 진행됩니다." (정확한 사실 응답) - 질문 2:
오늘 파리 날씨는 어때?
👉 기대 답변: "참고문서에 없는 정보이므로 알 수 없습니다." (문서에 없는 무관한 답변 차단 및 방어 성공)
4. Streamlit 백그라운드 상시 구동 및 관리 (SSH 종료 후 유지)
학생들이 터미널 세션을 종료해도 웹 서버가 꺼지지 않고 계속 기동되도록 관리하는 두 가지 백그라운드 구동 기법입니다.
방법 A: nohup 사용 (단일 명령어로 간편 구동)
- 백그라운드 백스테이지 실행:
nohup python3 -m streamlit run app.py --server.address 0.0.0.0 > streamlit.log 2>&1 & - 동작 로그 실시간 조회:
tail -f streamlit.log - 백그라운드 서버 프로세스 종료:
- 가장 쉬운 한 줄 종료 (추천 🌟):
pkill -f streamlit - 프로세스 번호(PID) 조회 후 수동 종료:
# 1) 백그라운드 프로세스 번호(PID) 조회 ps -ef | grep streamlit # 2) 해당하는 PID 강제 종료 (예: PID가 5678인 경우) kill -9 5678
- 가장 쉬운 한 줄 종료 (추천 🌟):
방법 B: screen 사용 (가상 다중 창 터미널 관리)
- 독립된 백그라운드 가상 스크린 개설:
screen -S streamlit_session - 스크린 진입 상태에서 일반 서버 실행:
python3 -m streamlit run app.py --server.address 0.0.0.0 - 스크린에서 탈출하여 백그라운드 활성화 (Detached):
- 키보드 단축키:
Ctrl + A를 차례로 누른 뒤 손을 떼고 바로D키를 입력합니다.
- 키보드 단축키:
- 언제든지 백그라운드 스크린 세션으로 재진입:
screen -r streamlit_session - 세션 완전 소멸 및 서버 종료:
- 스크린에 재진입한 상태에서
Ctrl + C로 중단하고exit를 입력해 터미널 창을 닫아 소멸시킵니다.
- 스크린에 재진입한 상태에서